package service

import (
	"context"
	"fmt"
	"regexp"

	"engine/db"
	"engine/log"
	"engine/misc"
	"gitee.com/ameise84/e3dds"
	time "gitee.com/ameise84/e3time"
	"gitee.com/ameise84/e3utils/errors"
	"gitee.com/ameise84/e3utils/str_conv"
	"github.com/redis/go-redis/v9"
	"google.golang.org/protobuf/proto"
	"servers/models/error_code"
	"servers/models/protos/google/pb/common_protos"
	"servers/models/protos/google/pb/net_protos"
	"servers/models/protos/google/pb/rpc_protos"
	"servers/models/redis_help"
)

func init() {
	service.RegisterProtocolHandler(&HandlerGame{})
}

type HandlerGame struct{}

func (g *HandlerGame) DisconnectCast(a e3dds.Behavior, req e3dds.Request) bool {
	a.Push(misc.PbMsgToRpcMsg(&common_protos.GlobalResponseError{Code: error_code.ErrorCodeOtherTokenLogin}))
	log.GetLogger().Debug("in other gateway Disconnect").Println()
	return false // false to close this actor
}

func (g *HandlerGame) GameTokenLoginReq(a e3dds.Behavior, req e3dds.Request) bool {
	player := a.GetActor().(*Actor)

	if player.IsAuth {
		req.ResponseMsg(misc.PbMsgToRpcMsg(&net_protos.GameTokenLoginRsp{Code: error_code.ErrorCodeLoginStatus}))
		return true
	}

	msg := &net_protos.GameTokenLoginReq{}
	_ = proto.Unmarshal(req.GetBody(), msg)
	dbCli := db.GetInstance()
	redisCli := db.GetRedis()

	token := str_conv.ToString(msg.Token[:])
	ctx := context.Background()
	r, err := redisCli.Get(ctx, redis_help.KeyTokenToUserID(token)).Result()
	if err != nil {
		req.ResponseMsg(misc.PbMsgToRpcMsg(&net_protos.GameTokenLoginRsp{Code: error_code.ErrorCodeTokenExpired}))
		return true
	}

	k := fmt.Sprintf(redis_help.LoginGateway, r)
	mu := dbCli.Locker(k)
	mu.Lock()
	defer mu.Unlock()

	other, err := redisCli.GetSet(ctx, k, a.GetSource().ToJson()).Result()
	if err != nil {
		if !errors.Is(err, redis.Nil) {
			req.ResponseMsg(misc.PbMsgToRpcMsg(&net_protos.GameTokenLoginRsp{Code: error_code.ErrorCodeServiceBusy}))
			return true
		}
	} else {
		var otherSource *e3dds.Source
		if otherSource, err = e3dds.SourceFromJson(other); err != nil {
			log.GetLogger().WarnPrintf("in other gateway unmrshal failed:%s", other)
		} else {
			log.GetLogger().WarnPrintf("in other gateway:%v local:%v", otherSource, a.GetSource())
			protoc, body := misc.PbMsgToRpcMsg(&rpc_protos.DisconnectCast{})
			a.CastFix(otherSource, protoc, body)
		}
	}

	re := regexp.MustCompile(`^(\d+):(\d+)$`)
	rs := re.FindStringSubmatch(r)
	if len(rs) != 3 {
		req.ResponseMsg(misc.PbMsgToRpcMsg(&net_protos.GameTokenLoginRsp{Code: error_code.ErrorCodeServiceBusy}))
		return true
	}
	player.IsAuth = true
	player.Account.BusinessID = rs[1]
	player.Account.UserID = rs[2]
	player.Account.GameKindID = msg.ServerID
	pipe := redisCli.Pipeline()
	f1 := redis_help.Join(0, player.Account.BusinessID)
	f2 := redis_help.Join(player.Account.GameKindID, player.Account.BusinessID)
	ko := redis_help.KeyOnlineUserTime()
	r1 := pipe.HIncrBy(ctx, ko, f1, 1)
	r2 := pipe.HIncrBy(ctx, ko, f2, 1)
	if _, err = pipe.Exec(ctx); err != nil {
		log.GetLogger().WarnPrintf("login updata exec failed:%v", err)
	} else {
		now := time.Now()
		zero := time.ZeroTimeUTC(now)
		zeroTime := zero.Unix()
		pipe2 := redisCli.Pipeline()
		rr1, _ := r1.Result()
		rr2, _ := r2.Result()
		km := redis_help.KeyOnlineUserMax(zeroTime)
		pipe2.Eval(ctx, redis_help.HSetMaxLoginCntScript, []string{km, f1}, []any{rr1})
		pipe2.Eval(ctx, redis_help.HSetMaxLoginCntScript, []string{km, f2}, []any{rr2})
		exp := zero.Add(2 * time.Day)
		pipe2.ExpireAt(ctx, km, exp)
		if _, err = pipe2.Exec(ctx); err != nil {
			log.GetLogger().WarnPrintf("login updata script exec failed:%v", err)
		}
	}

	req.ResponseMsg(misc.PbMsgToRpcMsg(&net_protos.GameTokenLoginRsp{UserID: str_conv.ToUint64(player.Account.UserID)}))
	return true
}
